﻿//++
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Module Name:
//
//  UpdateSerializer.cs
//    
// Abstract:
//
//  This module implements the UpdateSerializer class. This class is responsible for serializing the execution of all tasks 
//  for a given user.
//
//--

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

using Microsoft.OfficeCommunicationsServer.Applications.Common;

namespace Microsoft.EnhancedReminders.Scheduler
{
    /// <summary>
    /// Serializes execution of all tasks for a given user.
    /// </summary>
    internal class TaskSerializer : IDisposable
    {
        // List of pending tasks to be executed
        private List<Task> _taskList;
        
        // Current task being executed
        private Task _currentTask;

        // Timer used to detecet blocked tasks
        private Timer _timer;

        // Synchronizes access to the task list and timer
        private object _syncRoot = new object();

        /// <summary>
        /// ctor.
        /// </summary>
        internal TaskSerializer()
        {
            _taskList = new List<Task>();
            _timer = new Timer(new TimerCallback(TimerCallback));
        }

        /// <summary>
        /// Disposes of the timer and clears the list of pending tasks.
        /// </summary>
        public void Dispose()
        {
            lock (_syncRoot)
            {
                // Dispose of timer
                if (_timer != null)
                {
                    _timer.Dispose();
                    _timer = null;
                }

                // Ensure no more tasks are executed
                if (_taskList != null)
                {
                    _taskList.Clear();
                    _taskList = null;
                }
            }
        }

        /// <summary>
        /// Serializes the execution of the specified task.
        /// </summary>
        /// <param name="task">Task to be executed</param>
        internal void Execute(Task task)
        {
            Debug.Assert(task != null, "task != null");

            Execute(task, false);
        }

        /// <summary>
        /// Serializes the execution of the specified task.
        /// </summary>
        /// <param name="task">Task to be executed</param>
        /// <param name="doNext">Indicates if the task should be the next one to be executed</param>
        internal void Execute(Task task, bool doNext)
        {
            Debug.Assert(task != null, "task != null");

            task.Completed += Task_Completed;

            bool processNow = false;

            lock (_syncRoot)
            {
                if (_taskList == null)
                {
                    // Already disposed so ignore any further tasks
                    return;
                }

                if (_currentTask != null)
                {
                    if (doNext)
                    {
                        // A task is already being executed so add the new task to the start of the list
                        _taskList.Insert(0, task);
                    }
                    else
                    {
                        // A task is already being executed so add the new task to the end of the list
                        _taskList.Add(task);
                    }
                }
                else
                {
                    // No task was being executed so it is okay to execute this task now
                    _currentTask = task;
                    processNow = true;
                    StartTimer();
                }
            }

            if (processNow)
            {
                // Execute the specified task now
                task.Begin();
            }
        }

        /// <summary>
        /// Raised when an irrecoverable error occurs.
        /// </summary>
        public event EventHandler<SchedulerEventArgs> Failed;

        /// <summary>
        /// Handles completion of a task.
        /// </summary>
        /// <param name="sender">Task</param>
        /// <param name="e">Event args</param>
        private void Task_Completed(object sender, TaskCompletedEventArgs e)
        {
            Debug.Assert(sender != null, "sender != null");
            Debug.Assert(sender == _currentTask, "sender == _currentTask");

            sender.AssertCast<Task>().Completed -= Task_Completed;

            lock (_syncRoot)
            {
                if (_taskList != null && _taskList.Count > 0)
                {
                    // Get the next pending task
                    _currentTask = _taskList[0];
                    Debug.Assert(_currentTask != null, "_currentTask != null");
                    _taskList.RemoveAt(0);
                    StartTimer();
                }
                else
                {
                    // No pending tasks
                    _currentTask = null;
                    StopTimer();
                }
            }

            if (_currentTask != null)
            {
                // Execute the next task now. Tasks are executed outside the lock to reduce contention and the 
                // potential for deadlocks.
                _currentTask.Begin();
            }
        }

        /// <summary>
        /// Starts the timer.
        /// </summary>
        /// <remarks>
        /// The timer is started whenever a task is executed. This is to ensure blocked tasks are detected.
        /// </remarks>
        private void StartTimer()
        {
            if (_timer != null)
            {
                // 5 minutes should be sufficient for any task to execute
                _timer.Change(5 * 60 * 1000 /* 5 minutes */, Timeout.Infinite);
            }
        }

        /// <summary>
        /// Stops the timer.
        /// </summary>
        /// <remarks>
        /// The timer is stopped when no tasks are executing.
        /// </remarks>
        private void StopTimer()
        {
            if (_timer != null)
            {
                // Stop the timer by setting the timeout to infinite
                _timer.Change(Timeout.Infinite, Timeout.Infinite);
            }
        }

        /// <summary>
        /// Timer callback.
        /// </summary>
        /// <param name="state">Not used</param>
        /// <remarks>
        /// If the timer expires it must mean that a task is blocked. This is an unrecoverable error.
        /// </remarks>
        private void TimerCallback(object state)
        {
            Task currentTask = _currentTask;

            if (currentTask != null)
            {
                Logger.WriteTraceWarning(currentTask.Context, "Task did not complete: {0}", currentTask);

                // This is an unrecoverable error
                RaiseFailed(new SchedulerEventArgs(currentTask.Context));
            }
        }
     
        /// <summary>
        /// Raises the Failed event.
        /// </summary>
        /// <param name="e">Event args</param>
        private void RaiseFailed(SchedulerEventArgs e)
        {
            Debug.Assert(e != null, "e != null");

            EventHandler<SchedulerEventArgs> failed = Failed;

            if (failed != null)
            {
                failed(this, e);
            }
        }
    }
}
